package junit;

import beantools.ClassFinder;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class FlexibleSuite extends Suite {

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Regex {
        public String include() default ".*";
        public String exclude() default "";
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Annotations {
        public Class[] value() default {Test.class};
    }


    @SuppressWarnings({"UnusedDeclaration"})
    public FlexibleSuite(Class<?> klass, RunnerBuilder builder) throws InitializationError, ClassNotFoundException, IOException {
        super(builder, klass, new ClassFinder(include(klass), exclude(klass), annotations(klass), false).find().toArray(new Class[0]));
        sort(new Sorter(statsBasedComparator()));
    }


    private static Class[] annotations(Class<?> klass) {
        if(! klass.isAnnotationPresent(Annotations.class)) return new Class[]{Test.class};

        return klass.getAnnotation(Annotations.class).value();
    }

    private static String include(Class<?> klass) {
        if(!klass.isAnnotationPresent(Regex.class)) return ".*";

        return klass.getAnnotation(Regex.class).include();
    }

    private static String exclude(Class<?> klass) {
        if(!klass.isAnnotationPresent(Regex.class)) return "";

        return klass.getAnnotation(Regex.class).exclude();
    }

    @Override
    public void run(RunNotifier notifier) {
        final TestStatistics statistics = new TestStatistics();

        notifier.addFirstListener(new RunListener() {
            Map<String, Long> starts = new HashMap<String, Long>();

            @Override
            public void testStarted(Description description) throws Exception {
                starts.put(className(description), System.nanoTime());
            }

            @Override
            public void testFinished(Description description) throws Exception {
                long finished = System.nanoTime();
                String className = className(description);
                long duration = finished - starts.get(className);
                statistics.addTestDuration(className, duration);
            }

            @Override
            public void testFailure(Failure failure) throws Exception {
                statistics.add(failure);
            }
        });

        super.run(notifier);

        statistics.printReport();
        statistics.saveTo(statisticsFileName());
    }

    private Comparator<Description> statsBasedComparator() {
        return TestStatistics.loadFromFile(statisticsFileName()).comparator();
    }

    static String className(Description description) {
        if (!description.getDisplayName().contains("(")) return description.getDisplayName();
        return description.getDisplayName().split("\\(")[1].split("\\)")[0];
    }

    private String statisticsFileName() {
        return ".failed-tests";
    }
}
